#!/usr/bin/env python3
#
# Copyright 2020-2025 The Khronos Group Inc.
#
# SPDX-License-Identifier: Apache-2.0

# Build a spec with requested extension sets and options.
#
# Usage: makeSpec script-options make-options
# Script options are parsed by this script before invoking 'make':
#   -genpath path - directory for generated files and outputs
#   -spec core - make a spec with no extensions (default)
#   -spec khr - make a spec with all KHR extensions
#   -spec ratified - make a spec with all ratified (KHR + some EXT) extensions
#   -spec all - make a spec with all registered extensions
#   -version {1.0 | 1.1 | 1.2 | 1.3 | 1.4 | sc1.0} - make a spec with this core version
#   -ext name - add specified extension and its dependencies
#   -clean - clean generated files before building
#   -registry path - API XML to use instead of default
#   -apiname name - API name to use instead of default
#   -test - Build the test spec instead
#   -v - verbose, print actions before executing  them
#   -n - dry-run, print actions instead of executing them
# make-options - all other options are passed to 'make', including
# requested build targets

import argparse, copy, io, os, re, string, subprocess, sys

def execute(args, results):
    if results.verbose or results.dryrun:
        print("'" + "' '".join(args) + "'")
    if not results.dryrun:
        subprocess.check_call(args)

if __name__ == '__main__':
    parser = argparse.ArgumentParser()

    parser.add_argument('-clean', action='store_true',
                        help='Clean generated files before building')
    parser.add_argument('-extension', action='append',
                        default=[],
                        help='Specify a required extension or extensions to add to targets')
    parser.add_argument('-genpath', action='store',
                        default='gen',
                        help='Path to directory containing generated files')
    parser.add_argument('-spec', action='store',
                        choices=[ 'core', 'khr', 'ratified', 'all' ],
                        default='core',
                        help='Type of spec to generate')
    parser.add_argument('-version', action='store',
                        choices=[ '1.0', '1.1', '1.2', '1.3', '1.4', 'sc1.0', 'vkb1.4', 'vkc1.4', 'vkg1.4'],
                        default='1.4',
                        help='Type of spec to generate')
    parser.add_argument('-registry', action='store',
                        default=None,
                        help='Path to API XML registry file specifying version and extension dependencies')
    parser.add_argument('-apiname', action='store',
                        default=None,
                        help='API name to generate')
    parser.add_argument('-test', action='store_true',
                        help='Build the test spec instead of the Vulkan spec')
    parser.add_argument('-n', action='store_true', dest='dryrun',
                        help='Only prints actions, do not execute them')
    parser.add_argument('-v', action='store_true', dest='verbose',
                        help='Print actions before executing them')

    (results, options) = parser.parse_known_args()

    # Ensure genpath is an absolute path, not relative
    if results.genpath[0] != '/':
        results.genpath = os.getcwd() + '/' + results.genpath

    # Look for scripts/extdependency.py
    # This requires makeSpec to be invoked from the repository root, but we
    # could derive that path.
    sys.path.insert(0, 'scripts')
    from extdependency import ApiDependencies
    deps = ApiDependencies(results.registry, results.apiname)

    # List of versions to build with from the requested -version
    # This is constructed from the XML version dependencies
    versionDict = {
        '1.0' : [ 'VK_VERSION_1_0' ],
        '1.1' : [ 'VK_VERSION_1_1' ],
        '1.2' : [ 'VK_VERSION_1_2' ],
        '1.3' : [ 'VK_VERSION_1_3' ],
        '1.4' : [ 'VK_VERSION_1_4' ],
        'sc1.0' : [ 'VKSC_VERSION_1_0' ],
        'vkb1.4' : [ 'VK_BASE_VERSION_1_4' ],
        'vkc1.4' : [ 'VK_COMPUTE_VERSION_1_4' ],
        'vkg1.4' : [ 'VK_GRAPHICS_VERSION_1_4' ],
    }
    depversions = set()
    reqVer = versionDict[results.version][0]
    if reqVer in deps.allVersions():
        depversions.add(reqVer)
        for dep in deps.versionChildren(reqVer):
            if dep in deps.allVersions():
                depversions.update({dep})
    else:
        apitype = f'default apiname' if results.apiname is None else f'requested apiname {results.apiname}'
        raise Exception(f'ERROR: unknown version {results.version} for {apitype}')

    versions = 'VERSIONS={}'.format(' '.join(sorted(depversions)))

    # List of extensions to build with from the requested -spec
    # Also construct a spec title
    # This should respect version dependencies as well
    if results.spec == 'core':
        title = ''
        exts = set()
    elif results.spec == 'khr':
        title = 'with all KHR extensions'
        exts = set(deps.khrExtensions())
    elif results.spec == 'ratified':
        title = 'with all ratified extensions'
        exts = set(deps.ratifiedExtensions())
    elif results.spec == 'all':
        title = 'with all registered extensions'
        exts = set(deps.allExtensions())

    # List of explicitly requested extension and all its supported dependencies
    extraexts = set()
    for name in results.extension:
        if name in deps.allExtensions():
            extraexts.add(name)
            for dep in deps.children(name):
                if dep in deps.allExtensions():
                    extraexts.update({dep})
        else:
            raise Exception(f'ERROR: unknown extension {name}')

    # See if any explicitly requested extensions are not implicitly requested
    # Add any such extensions to the spec title
    extraexts -= exts
    if len(extraexts) > 0:
        exts.update(extraexts)
        if title != '':
            title += ' and ' + ', '.join(sorted(extraexts))
        else:
            title += 'with ' + ', '.join(sorted(extraexts))

    if title != '':
        title = '(' + title + ')'

    # Finally, actually invoke make as needed for the targets
    args = [ 'make', 'GENERATED=' + results.genpath ]

    if results.clean:
        # If OUTDIR is set on the command line, pass it to the 'clean'
        # target so it is cleaned as well.
        cleanopts = ['clean']
        for opt in options:
            if opt[:7] == 'OUTDIR=':
                cleanopts.append(opt)
        try:
            execute(args + cleanopts, results)
        except:
            sys.exit(1)

    # Use the test spec if specified.  This is used solely by self tests.
    rootdir = os.path.dirname(os.path.abspath(__file__))
    if results.test:
        # Set the spec source to the test spec
        args.append(f'SPECSRC={rootdir}/build_tests/testspec.adoc')
        args.append(f'SPECDIR={rootdir}/build_tests/')
        # Make sure the build is invariant
        args.append('SPECREVISION=1.2.3')
        args.append('SPECDATE=\\"2100-11-22 00:33:44Z\\"')
        args.append('SPECREMARK=\\"test build\\"')

    args.append(versions)

    # The actual target
    if len(exts) > 0:
        args.append(f'EXTENSIONS={" ".join(sorted(exts))}')
    args.append(f'APITITLE={title}')
    args += options

    try:
        execute(args, results)
    except:
        sys.exit(1)
